#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <string.h>
#include <errno.h>
#include <signal.h>

char *shm_addr; // 共享内存的首地址
int semid; // 信号量id
int shmid;

// 信号量共用体
union semun {
	int              val;    /* 信号量的值 */
	struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
	unsigned short  *array;  /* Array for GETALL, SETALL */
	struct seminfo  *__buf;  /* Buffer for IPC_INFO
								(Linux-specific) */
}; 

// p操作
void sem_p(int semid, int num)
{
	struct sembuf action; // 信号量动作结构体
	action.sem_num = num; // 信号量下标
	action.sem_op = -1; // p操作
	action.sem_flg = 0; // 默认阻塞
	// 对信号量下标为num的信号量做p操作
	semop(semid, &action, 1); // 1个元素
}

// v操作
void sem_v(int semid, int num)
{
	struct sembuf action; // 信号量动作结构体
	action.sem_num = num; // 信号量下标
	action.sem_op = 1; // v操作
	action.sem_flg = 0; // 默认阻塞
	// 对信号量下标为num的信号量做v操作
	semop(semid, &action, 1); // 1个元素
}

int main(int argc, char const *argv[])
{
	// 1.创建键值
	key_t key = ftok(".", 1);
	if(key == -1)
	{
		perror("ftok error\n");
		return -1;
	}
	// 2.创建共享内存，获取id号
	shmid = shmget(key, 1024, IPC_CREAT|IPC_EXCL|0666);
	if(shmid == -1 && errno == EEXIST)
	{
		shmid = shmget(key, 1024, 0666);
	}
	if(shmid == -1)
	{
		perror("shmget error\n");
		return -1;
	}
	// 3.映射共享内存到应用层
	shm_addr = (char *)shmat(shmid, NULL, 0);
	if(shm_addr == (void *)-1)
	{
		perror("shmat error\n");
		return -1;
	}
	// 4.创建信号量获取id号
	semid = semget(key, 2, IPC_CREAT|IPC_EXCL|0666);
	if(semid == -1)
	{
		if(errno == EEXIST)
		{
			semid = semget(key, 2, 0666);
			if(semid == -1)
			{
				perror("semget error\n");
				return -1;
			}
		}
	}
	else if(semid > 0) // 初始化
	{
		union semun a;
		a.val = 1;
		// 让下标为0的信号量初始化为1
		semctl(semid, 0, SETVAL, a);

		// 让下标为1的信号量初始化为0
		a.val = 0;
		semctl(semid, 1, SETVAL, a);
	}

	while(1)
	{
		sem_p(semid, 1); // 对1p操作
		printf("%s\n", shm_addr);
		sem_v(semid, 0); // 对0v操作
		// 注意，本进程在退出前，应执行一次v操作，避免对方阻塞，无法退出
		if(strcmp(shm_addr, "bye") == 0)
			break;

	}
	shmdt(shm_addr);
	shmctl(shmid, IPC_RMID, NULL);
	semctl(semid, 0, IPC_RMID, 0);

	return 0;
}
